#include "clkCamera.h"

clkCamera::clkCamera()
{
	m_type = 0;
	m_fov = 0;
	m_nearPlane = 0;
	m_farPlane = 0;
	m_aspectRatio = 0;
	m_yaw = 0;
	m_pitch = 0;
	m_roll = 0;
	m_attachDistance = 0;
	m_attachPosition = 0;

	memset(&m_eye,0,sizeof(XMFLOAT3A));
	memset(&m_look,0,sizeof(XMFLOAT3A));
	memset(&m_right,0,sizeof(XMFLOAT3A));
	memset(&m_up,0,sizeof(XMFLOAT3A));
	memset(&m_viewMatrix,0,sizeof(XMFLOAT4X4));
	memset(&m_perspectiveMatrix,0,sizeof(XMFLOAT4X4));
	memset(&m_orthogonalMatrix,0,sizeof(XMFLOAT4X4));

	XMStoreFloat4x4(&m_viewMatrix,XMMatrixIdentity());
	XMStoreFloat4x4(&m_perspectiveMatrix,XMMatrixIdentity());
	XMStoreFloat4x4(&m_orthogonalMatrix,XMMatrixIdentity());
}

clkCamera::~clkCamera()
{

}

void clkCamera::init(int a_type, V2DF a_dimension)
{
	init(a_type,
		(float)CLK_CAMERA_FOV,
		CLK_CAMERA_NEAR,
		CLK_CAMERA_FAR,
		a_dimension);
}

void clkCamera::init(
	int   a_type,
	float a_fov,
	float a_nearPlane,
	float a_farPlane,
	V2DF  a_dimension)
{
	m_type = a_type;
	m_fov = a_fov;
	m_nearPlane = a_nearPlane;
	m_farPlane = a_farPlane;
	m_dimension = a_dimension;
	m_aspectRatio = m_dimension.x/m_dimension.y;

	m_eye   = XMFLOAT3A(0,0,0);
	m_look  = XMFLOAT3A(0,0,1);
	m_up    = XMFLOAT3A(0,1,0);
	m_right = XMFLOAT3A(1,0,0);

	update();
}

void clkCamera::shut()
{

}

void clkCamera::update()
{
	switch(m_type)
	{
	case CLK_CAMERA_FIRSTPERSON: updateFirstPerson(); break;
	case CLK_CAMERA_THIRDPERSON: updateThirdPerson(); break;
	default: return;
	}

	updateOrthogonal();
	updateFrustum();
}

void clkCamera::updateOrthogonal()
{
	XMStoreFloat4x4(&m_orthogonalMatrix,XMMatrixOrthographicOffCenterLH(
		0,m_dimension.x,-m_dimension.y,0,0.0f,m_farPlane));
}

void clkCamera::updateFirstPerson()
{
	XMFLOAT3 pos = m_eye;
	m_eye   = XMFLOAT3(0,0,0);
	m_look  = XMFLOAT3(0,0,1);
	m_up    = XMFLOAT3(0,1,0);
	m_right = XMFLOAT3(1,0,0);

	degreeClamp(m_pitch);
	degreeClamp(m_yaw);
	degreeClamp(m_roll);

	XMFLOAT4 quaternion;
	XMStoreFloat4(&quaternion,XMQuaternionRotationRollPitchYaw(
		CLK_RADIAN(m_pitch),CLK_RADIAN(m_yaw),CLK_RADIAN(m_roll)));
	XMStoreFloat3(&m_look,XMVector3Rotate(XMLoadFloat3(&m_look),XMLoadFloat4(&quaternion)));
	XMStoreFloat3(&m_up,XMVector3Rotate(XMLoadFloat3(&m_up),XMLoadFloat4(&quaternion)));
	XMStoreFloat3(&m_right,XMVector3Rotate(XMLoadFloat3(&m_right),XMLoadFloat4(&quaternion)));

	m_eye = pos;
	XMStoreFloat4x4(&m_perspectiveMatrix,XMMatrixPerspectiveFovLH(
		m_fov,m_aspectRatio,m_nearPlane,m_farPlane));
	XMStoreFloat4x4(&m_viewMatrix,XMMatrixLookToLH(
		XMLoadFloat3(&m_eye),
		XMLoadFloat3(&m_look),
		XMLoadFloat3(&m_up)));
}

void clkCamera::updateThirdPerson()
{
	m_eye   = XMFLOAT3(0,0,0);
	m_look  = XMFLOAT3(0,0,1);
	m_up    = XMFLOAT3(0,1,0);
	m_right = XMFLOAT3(1,0,0);

	degreeClamp(m_pitch);
	degreeClamp(m_yaw);
	degreeClamp(m_roll);

	XMFLOAT4 quaternion;
	XMStoreFloat4(&quaternion,XMQuaternionRotationRollPitchYaw(
		CLK_RADIAN(m_pitch),CLK_RADIAN(m_yaw),CLK_RADIAN(m_roll)));
	XMStoreFloat3(&m_look,XMVector3Rotate(XMLoadFloat3(&m_look),XMLoadFloat4(&quaternion)));
	XMStoreFloat3(&m_up,XMVector3Rotate(XMLoadFloat3(&m_up),XMLoadFloat4(&quaternion)));
	XMStoreFloat3(&m_right,XMVector3Rotate(XMLoadFloat3(&m_right),XMLoadFloat4(&quaternion)));

	m_eye = XMFLOAT3(
		m_eye.x+(-m_look.x*m_attachDistance),
		m_eye.y+(-m_look.y*m_attachDistance),
		m_eye.z+(-m_look.z*m_attachDistance));
	if(m_attachPosition)
		m_eye = XMFLOAT3(
		m_eye.x+m_attachPosition->x,
		m_eye.y+m_attachPosition->y,
		m_eye.z+m_attachPosition->z);

	XMStoreFloat4x4(&m_perspectiveMatrix,XMMatrixPerspectiveFovLH(
		m_fov,m_aspectRatio,m_nearPlane,m_farPlane));
	XMStoreFloat4x4(&m_viewMatrix,XMMatrixLookToLH(
		XMLoadFloat3(&m_eye),
		XMLoadFloat3(&m_look),
		XMLoadFloat3(&m_up)));
}

void clkCamera::updateFrustum()
{
	XMFLOAT4X4 viewProj;
	XMStoreFloat4x4(&viewProj,XMMatrixMultiply(
		XMLoadFloat4x4(&m_viewMatrix),
		XMLoadFloat4x4(&m_perspectiveMatrix)));

	// Left plane
	m_frustum[0].x = viewProj._14 + viewProj._11;
	m_frustum[0].y = viewProj._24 + viewProj._21;
	m_frustum[0].z = viewProj._34 + viewProj._31;
	m_frustum[0].w = viewProj._44 + viewProj._41;

	// Right plane
	m_frustum[1].x = viewProj._14 - viewProj._11;
	m_frustum[1].y = viewProj._24 - viewProj._21;
	m_frustum[1].z = viewProj._34 - viewProj._31;
	m_frustum[1].w = viewProj._44 - viewProj._41;

	// Top plane
	m_frustum[2].x = viewProj._14 - viewProj._12;
	m_frustum[2].y = viewProj._24 - viewProj._22;
	m_frustum[2].z = viewProj._34 - viewProj._32;
	m_frustum[2].w = viewProj._44 - viewProj._42;

	// Bottom plane
	m_frustum[3].x = viewProj._14 + viewProj._12;
	m_frustum[3].y = viewProj._24 + viewProj._22;
	m_frustum[3].z = viewProj._34 + viewProj._32;
	m_frustum[3].w = viewProj._44 + viewProj._42;

	// Near plane
	m_frustum[4].x = viewProj._13;
	m_frustum[4].y = viewProj._23;
	m_frustum[4].z = viewProj._33;
	m_frustum[4].w = viewProj._43;

	// Far plane
	m_frustum[5].x = viewProj._14 - viewProj._13;
	m_frustum[5].y = viewProj._24 - viewProj._23;
	m_frustum[5].z = viewProj._34 - viewProj._33;
	m_frustum[5].w = viewProj._44 - viewProj._43;

	//normalize planes
	for(int i=0;i<6;i++)
		XMStoreFloat4(&m_frustum[i],XMPlaneNormalize(XMLoadFloat4(&m_frustum[i])));
}

void clkCamera::screenToView(
	V2DF  a_point,
	V3DF* a_origin,
	V3DF* a_direction)
{
	//temp variables
	float w = m_dimension.x;
	float h = m_dimension.y;
	XMFLOAT4X4 iview;
	XMFLOAT3 vec, ori, dir;

	//convert screen coordinate to world coordinate
	vec.x = (((2.0f*a_point.x)/w)-1.0f)/m_perspectiveMatrix._11;
	vec.y = -(((2.0f*a_point.y)/h)-1.0f)/m_perspectiveMatrix._22;
	vec.z = 1.0f;

	//calculate the direction and origin of the converted coordinate
	XMStoreFloat4x4(&iview,XMMatrixInverse(0,XMLoadFloat4x4(&m_viewMatrix)));
	dir.x = vec.x*iview._11+vec.y*iview._21+vec.z*iview._31;
	dir.y = vec.x*iview._12+vec.y*iview._22+vec.z*iview._32;
	dir.z = vec.x*iview._13+vec.y*iview._23+vec.z*iview._33;
	ori.x = iview._41;
	ori.y = iview._42;
	ori.z = iview._43;
	XMStoreFloat3(&dir,XMVector3Normalize(XMLoadFloat3(&dir)));

	//fill up return values
	if(a_origin) (*a_origin).set((*((V3DF*)&ori)));
	if(a_direction) (*a_direction).set((*((V3DF*)&dir)));
}

bool clkCamera::tritest(
	XMFLOAT3* a_triangle,
	XMFLOAT3  a_origin,
	XMFLOAT3  a_direction,
	float*    a_distance)
{
	return TriangleTests::Intersects(
		XMLoadFloat3(&a_origin),
		XMLoadFloat3(&a_direction),
		XMLoadFloat3(&a_triangle[0]),
		XMLoadFloat3(&a_triangle[1]),
		XMLoadFloat3(&a_triangle[2]),
		(*a_distance));
}

bool clkCamera::raytest(
	V3DF   a_origin,
	V3DF   a_direction,
	V3DF*  a_vertices,
	int*   a_indices,
	float* a_distance)
{
	//temp variables
	XMFLOAT3 ori(((float*)&a_origin));
	XMFLOAT3 dir(((float*)&a_direction));

	//perform ray testing
	bool hit = false;
	float dis = CLK_FLOAT_MAX;
	for(int i=0;i<24;i+=4)
	{
		//construct triangle A
		XMFLOAT3 triA[3] = 
		{
			XMFLOAT3(((float*)&a_vertices[a_indices[i]])),
			XMFLOAT3(((float*)&a_vertices[a_indices[i+1]])),
			XMFLOAT3(((float*)&a_vertices[a_indices[i+2]])),
		};

		//construct triangle B
		XMFLOAT3 triB[3] = 
		{
			XMFLOAT3(((float*)&a_vertices[a_indices[i]])),
			XMFLOAT3(((float*)&a_vertices[a_indices[i+2]])),
			XMFLOAT3(((float*)&a_vertices[a_indices[i+3]])),
		};

		//test triangle A
		float tdis = 0.0f;
		if(tritest(triA,ori,dir,&tdis))
		{
			hit = true;
			if(i==0) dis = tdis;
			dis = (tdis<dis)?tdis:dis;
		}
		else if(tritest(triB,ori,dir,&tdis))
		{
			hit = true;
			if(i==0) dis = tdis;
			dis = (tdis<dis)?tdis:dis;
		}
	}

	if(a_distance) (*a_distance) = dis;

	return hit;
}